/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/

/**
 * @brief Implement the class backpatcher.
 *
 * @author  WXQ#XDU
 * @date    2025.01
 */


#include "_emit_inner.h"
#include "../../include/amcc_hashmap.h"


// ------------------------------------------------------------------------
//   Part I   memory pool for storing all chains
//

// There are several strategies to support sharing a chain across
// multiple parse tree nodes and without "double-free" bug on chains.
//
// Here are two strategies:
// 1. reference-counter based tech, like t_codeattr in codemgr.h;
// 2. memory-pool based tech,  be used below.


// The chain set, a memory pool.
// This pool only stores E.tc, E.fc and S.nc.
//
// The conceptional type is  hash_map<list*, list*>,
// a list is both the Key and the mapped Value.
// REF: java.util.HashSet in JDK.

static t_hashmap_ptr  gChainSet;

// a simple hash function
static t_hashcode chainSet_keyhash(t_map_key key) {
    return (ptrdiff_t)key & 0x7FFFFFFF;
}

// Comparator of keys
static int chainSet_keycomp(t_map_key K1, t_map_key K2) {
    ptrdiff_t delta = K1 - K2;
    if ( delta == 0 ) return 0;
    if ( delta >  0 ) return 1;
    return -1;
}

// Deallocator for elements in memory pool
static void chainSet_free(t_map_key K , t_map_value V) {
    t_list_ptr chain = (t_list_ptr)V;
    list_destroy( chain );
}


// Initialization module level data
void  backpatch_module_init() {
    gChainSet = hashmap_new(50, chainSet_keyhash,
                            chainSet_keycomp, chainSet_free);
    log_trace("  Chains pool has initialized");
}

// Destroy module level data
void  backpatch_module_clean() {
    hashmap_clean( gChainSet );
    log_trace("  Chains pool has destroyed");
}


// Stores a chain into memory pool
static void chainSet_put(t_list_ptr chain) {
    if ( chain == NULL ) return;
    hashmap_put(gChainSet, chain, chain);
}



// ------------------------------------------------------------------------
//   Part II   Operations for make-chain, merge, back-patching
//


#define  NEW_EMPTY_CHAIN()  list_new(NULL)


t_list_ptr  emitter_makeChain(t_ircode_ptr pGotoCode, const char * chainName) {
    t_list_ptr chain = NEW_EMPTY_CHAIN();
    chainSet_put(chain);      // Stores the chain into memory pool
    if (NULL == pGotoCode) {  // just create an empty chain
        log_trace("    makeChain: empty chain %tx for %s", chain, chainName);
        return chain;
    }

    if (   pGotoCode->opcode <= OP_GOTO_MIN_
                || pGotoCode->opcode >= OP_GOTO_MAX_ )
    {
        ERROR_REPORT_BUG("makeChain: for a NON-goto instruction"
                         ", opcode is %d", pGotoCode->opcode);
    }
    else {
        list_append(chain, pGotoCode);
        log_trace("    makeChain: %s (%tx) ,records code %u",
                    chainName,  chain,  pGotoCode->id);
    }
    return chain;
}


t_list_ptr emitter_add2Chain( t_list_ptr targetChain,
                              t_ircode_ptr pGotoCode,
                              const char * chainName )
{
    if (NULL == pGotoCode)
        return targetChain;

    if (NULL == targetChain) {
        targetChain = emitter_makeChain(pGotoCode, chainName);
    } else {
        list_prepend(targetChain, pGotoCode);
        log_trace("        add code %u to chain %s (%tx)",
                  pGotoCode->id, chainName, targetChain);
    }

    return targetChain;
}


// helper callback operation for emitter_mergeChain()
static int  mergeChainNode_(t_list_app_value app_data, void* user_data) {
    t_ircode_ptr  pGotoCode = (t_ircode_ptr) app_data;
    if (NULL != pGotoCode) {
        list_append((t_list_ptr)user_data, pGotoCode);
        log_trace("        merge code %u to chain (%tx)", pGotoCode->id, user_data);
    }

    return 0;
}

t_list_ptr  emitter_mergeChain( t_list_ptr chain1,  const char * chainName1,
                                t_list_ptr chain2,  const char * chainName2,
                       t_list_ptr chainReturn, const char * chainNameReturn)
{
/*  No check, I want to show the log below
    if ( list_is_empty(chain1) && list_is_empty(chain2) )
        return chainReturn;
*/
    if (NULL == chainReturn ) {
        chainReturn = NEW_EMPTY_CHAIN();
        chainSet_put(chainReturn);
    }

    if ( chain2 != chainReturn && NULL != chainName2) {
        log_trace("    merge-chain: %s (%tx)  --> %s (%tx)",
                  chainName2,    chain2, chainNameReturn, chainReturn);
        list_foreach(chain2, mergeChainNode_, chainReturn);
    }
    if ( chain1 != chainReturn && NULL != chainName1 ) {
        log_trace("    merge-chain: %s (%tx)  --> %s (%tx)",
                  chainName1,    chain1, chainNameReturn, chainReturn);
        list_foreach(chain1, mergeChainNode_, chainReturn);
    }

    return chainReturn;
}


// helper callback operation for emitter_backpatch()
static int  backpatchNode_(t_list_app_value app_data, void* user_data) {
    t_ircode_ptr  pGotoCode = (t_ircode_ptr) app_data;
    if (NULL != pGotoCode) {
        t_ir_address_ptr  result = pGotoCode->address[AI_RES];
        result->value.targetCodeId = PTR_TO_INT(t_ircode_id, user_data);
        log_trace("    back-patched the [%u]_th goto: jump to code  %u",
                  pGotoCode->id,     result->value.targetCodeId );
    }

    return 0;
}

void emitter_backpatch(t_list_ptr chain, t_ircode_id targetCode,const char * chainName)
{
/*  No check, I want to show the log below
    if (NULL == chain) return;
*/

    log_trace("    back-patch:  fill %s (%tx) with code %u",
              chainName,  chain, targetCode);
    list_foreach_reverse(chain, backpatchNode_,
                         INT_TO_PTR(void*, targetCode));
}



// ------------------------------------------------------------------------
//   Part III   Operations on chains of labels in source program
//


t_list_ptr emitter_makeChainOfLabel(t_emitter_ptr pThis,
                                    t_tree_ptr labelNode,
                                    t_ircode_ptr  pGotoCode)
{
    t_map_key  labelName = (t_map_key)tree_get_markText(labelNode);

    // Get the existing chain, or create a new chain
    t_list_ptr  chain = (t_list_ptr)hashmap_find(pThis->currFuncInfo->chainOfLabels, labelName);
    if (NULL == chain) {
        // When emitter see the first forward reference to label,
        // create a new chain for this label, and
        // this chain will be destroyed in emitter_inner.c::emitter_popFuncInfo()
        //
        // These chains DON'T be stored in memory pool gChainSet!

        chain = NEW_EMPTY_CHAIN();
        hashmap_put(pThis->currFuncInfo->chainOfLabels, labelName, chain);
        log_trace("    makeChain: new chain (%tx) ,records code %u ,for label \"%s\"",
                  chain,   pGotoCode->id,    labelName);
    }
    else {
        log_trace("    makeChain: existing chain (%tx) ,records code %u ,for label \"%s\"",
                  chain,   pGotoCode->id,    labelName);
    }

    list_prepend(chain, pGotoCode);
    return chain;
}


void emitter_setTargetOfLabel(t_emitter_ptr pThis,
                              t_tree_ptr labelNode,
                              t_ircode_id targetCode)
{
    const char *  labelName = tree_get_markText(labelNode);

    // record the target code marked by this label
    hashmap_put(pThis->currFuncInfo->targetOfLabels,
                (t_map_key)labelName,
                INT_TO_PTR(t_map_value, targetCode));

    t_position pos = tree_get_startPosition(labelNode);
    log_trace("    Label \"%s\" marks code %u for source position  %d:%d",
              labelName, targetCode, pos.line,  pos.column);

    // want to do back-patch
    t_list_ptr  chain = (t_list_ptr)hashmap_find(
                            pThis->currFuncInfo->chainOfLabels,
                            (t_map_key) labelName);
    // BUT there is no any forward reference to this label
    if ( list_is_empty(chain) )
        return;

    emitter_backpatch(chain, targetCode, labelName);
    log_trace("    back-patched goto, jump to code  %u  marked by label \"%s\"",
              targetCode, labelName);

    // destroy useless chain
    hashmap_remove( pThis->currFuncInfo->chainOfLabels, (t_map_key) labelName);
}


t_ircode_id emitter_getTargetOfLabel(t_emitter_ptr pThis,
                                     t_tree_ptr labelNode)
{
    t_map_key  labelName = (t_map_key)tree_get_markText(labelNode);
    t_map_value appData  = hashmap_find(pThis->currFuncInfo->targetOfLabels, labelName);
    if (NULL == appData) {
        return 0;
    }
    t_ircode_id targetCode = PTR_TO_INT(t_ircode_id, appData);
    return targetCode;
}
